#!/usr/bin/python
# -*- coding: UTF-8 -*-

import sys
import serial
import struct
import ctypes
import binascii


def str_to_hex(s):
    return ' '.join([hex(ord(c)).replace('0x', '') for c in s])


def hex_to_str(s):
    return ''.join([chr(i) for i in [int(b, 16) for b in s.split(' ')]])


class OppleSerialMsg():
    """docstring for ClassName"""

    HEADER_FLAG = 0xbebebebe
    PROTOCOL_ID = 0x02
    VERSION = 0x01
    TAIL_FLAG = 0xeded

    FRAME_HEADER_LEN = 10
    FRAME_TAIL_LEN = 2

    PROP_MSGID_REQ = 0x8005
    PROP_MSGID_RSP = 0x8006
    OTA_MSGID_REQ = 0x8007
    OTA_MSGID_RSP = 0x8008
    ACTION_MSGID_REQ = 0x8011
    ACTION_MSGID_RSP = 0x8012

    # PROP_MSGID
    PROP_GET_REQ = 0x2011
    PROP_GET_RSP = 0x2012
    PROP_NOTIFY_REQ = 0x2015
    PROP_NOTIFY_RSP = 0x2016
    PROP_SET_REQ = 0x2017
    PROP_SET_RSP = 0x2018
    PROP_CUSTOM_GET_REQ = 0x2019
    PROP_CUSTOM_GET_RSP = 0x201a

    # ACTION_MSGID
    ACTION_ONOFF_REQ = 0x2211
    ACTION_ONOFF_RSP = 0x2212
    ACTION_REBOOT_REQ = 0x2231
    ACTION_REBOOT_RSP = 0X2232
    ACTION_TEST_REQ = 0xff01
    ACTION_TEST_RSP = 0xff02


    def __init__(self, com='COM4',
                    baudrate=9600,
                    bytesize=8,
                    parity='N',
                    stopbits=1):
        try:
            self.seqnum = 0
            self.read_timeout = 0.1
            self.serPort = serial.Serial(port=com,
                                    baudrate=baudrate,
                                    bytesize=bytesize,
                                    parity=parity,
                                    stopbits=stopbits,
                                    timeout=self.read_timeout)
        except:
            print "Open %s failed, please check if in using !" % com
            if self.serPort != None:
                self.serPort.close()
            os.system("pause")
            exit(-1)
        else:
            print "Open %s success" % com


    def __del__(self):
        if self.serPort != None and self.serPort.isOpen() == True:
            self.serPort.close()
            print "Close uart"


    def serial_send(self, data):
        while self.serPort.writable() == False:
            time.sleep(0.1)
        self.serPort.write(data)


    def crc(self, data):
        # length = len(data)
        # checksum = self.BLSP_CHECKSUM
        # for i in range(0, length):
        #     # print 'index: %d, checksum %04x' % (i, checksum)
        #     checksum += ord(data[i])
        #     checksum &= 0xFFFF

        # return checksum
        return 0x0000


    def craete_frame(self, payload):
        pkg = ctypes.create_string_buffer(self.FRAME_HEADER_LEN + len(payload) + self.FRAME_TAIL_LEN)

        offset = 0;

        struct.pack_into('<IBBHH', pkg, offset, self.HEADER_FLAG, self.PROTOCOL_ID, self.VERSION, len(payload), self.crc(payload))
        offset += self.FRAME_HEADER_LEN

        for i in range(0, len(payload)):
            struct.pack_into('<B', pkg, offset, ord(payload[i]))
            offset += 1

        struct.pack_into('<H', pkg, offset, self.TAIL_FLAG)
        offset += self.FRAME_TAIL_LEN

        return pkg


    def create_req(self, cmdid, seqnum, data):
        pkg = ctypes.create_string_buffer(4 + len(data))

        offset = 0;


        struct.pack_into('<HH', pkg, offset, cmdid, seqnum)
        offset += 4

        for i in range(0, len(data)):
            struct.pack_into('<B', pkg, offset, ord(data[i]))
            offset += 1

        return pkg


    def creat_resp(self, cmdid, seqnum, opcode, data):
        pkg = ctypes.create_string_buffer(5 + len(data))

        offset = 0;

        struct.pack_into('<HHB', pkg, offset, cmdid, seqnum, opcode)
        offset += 5

        for i in range(0, len(data)):
            struct.pack_into('<B', pkg, offset, ord(data[i]))
            offset += 1

        return pkg

    #
    # seqnum == 0 means not care
    #
    def find_msg(self, data, cmdid, seqnum):
        t_seqnum = seqnum
        payload = ''

        while len(data) >= self.FRAME_HEADER_LEN:
            # print 'get %d length data:' % len(data)
            # print binascii.b2a_hex(data)

            # !!! must add 'comma', or will be type of tuple
            hflag, pid, ver, datalen, crc = struct.unpack_from('<IBBHH', data, 0)
            if hflag != self.HEADER_FLAG or pid != self.PROTOCOL_ID or ver != self.VERSION:
                print "get hflag 0x%x pid 0x%x ver 0x%x err" % (hflag, pid, ver)
                data = data[1:] # filter one by one byte
            else:
                pkglen = self.FRAME_HEADER_LEN + datalen + self.FRAME_TAIL_LEN
                if len(data) < pkglen:
                    print 'length data %d is short than pkglen %d' % (len(data), pkglen)
                    break
                else:
                    # TODO: crc check

                    pkg = data[:pkglen]
                    print binascii.b2a_hex(pkg)
                    t_cmdid, t_seqnum = struct.unpack_from('<HH', pkg, self.FRAME_HEADER_LEN)

                    if (t_cmdid == cmdid and t_seqnum == seqnum) or (t_cmdid == cmdid and 0 == seqnum):
                        print 'get cmdid 0x%x, seqnum 0x%x' % (t_cmdid, t_seqnum)

                        reqcmd_list = [self.PROP_MSGID_REQ, self.OTA_MSGID_REQ, self.ACTION_MSGID_REQ]
                        if reqcmd_list.count(t_cmdid) > 0:
                            data = data[pkglen:]
                            payload = pkg[self.FRAME_HEADER_LEN + 4:(self.FRAME_HEADER_LEN+datalen)]
                            break
                        else:
                            data = data[pkglen:]
                            payload = pkg[self.FRAME_HEADER_LEN + 5:(self.FRAME_HEADER_LEN+datalen)]
                            break

                    print 'get cmdid 0x%x(should be 0x%x), seqnum 0x%x(should be 0x%x) err' % (t_cmdid, cmdid, t_seqnum, seqnum)
                    data = data[pkglen:]

        return data, t_seqnum, payload

    '''
    timeout: 0 will be waiting forever
    '''
    def should_recv(self, cmdid, seqnum, timeout):
        datapool = ''
        t_seq = 0
        payload = ''

        if timeout != 0:
            timeout_cnt = timeout / self.read_timeout
        else :
            timeout_cnt = 1

        # print 'timeout cnt %d' % timeout_cnt
        while timeout_cnt != 0:
            # print 'timeout cnt %d' % timeout_cnt
            if timeout != 0:
                # print 'timeout cnt %d' % timeout_cnt
                timeout_cnt -= 1

            if self.serPort.readable() == False:
                print 'serial port is not readable'
                time.sleep(self.read_timeout)
                continue

            data = self.serPort.read(256)
            if not data:
                continue

            datapool = datapool + data
            datapool, seqnum, payload = self.find_msg(datapool, cmdid, seqnum)

            if payload != '':
                break

        return t_seq, payload


    def get_boot_type(self, timeout):
        boottype = -1
        reqseq, payload = self.should_recv(self.PROP_MSGID_REQ, 0x0000, timeout)
        if payload == '':
            ret = '%s timeout in %dms' % (sys._getframe().f_code.co_name, timeout)
        else:
            print 'recv:' + binascii.b2a_hex(payload)
            propmsgid, infotype, datalen, boottype = struct.unpack_from('<HBBB', payload, 0x0)
            if propmsgid != self.PROP_NOTIFY_REQ or infotype != 0x73:
                ret = "prop msg id 0x%x infotype 0x%x err " % (propmsgid, infotype)
            else:
                # response
                prop = ctypes.create_string_buffer(3)
                offset = 0
                struct.pack_into('<HB', prop, offset, self.PROP_NOTIFY_RSP, 0x0)
                offset += 3
                self.serial_send(self.craete_frame(self.creat_resp(self.PROP_MSGID_RSP, reqseq, 0x0, prop)))
                ret = "success"

        return ret, boottype


    def get_network(self, timeout):
        netlink = -1
        rssi = -1
        reqseq, payload = self.should_recv(self.PROP_MSGID_REQ, 0x0000, timeout)
        if payload == '':
            ret = '%s timeout in %dms' % (sys._getframe().f_code.co_name, timeout)
        else:
            print 'recv:' + binascii.b2a_hex(payload)
            propmsgid, infotype, datalen, netlink, rssi = struct.unpack_from('<HBBBB', payload, 0x0)
            if propmsgid != self.PROP_NOTIFY_REQ or infotype != 0x72:
                ret = "prop msg id 0x%x infotype 0x%x err " % (propmsgid, infotype)
            else:
                # response
                prop = ctypes.create_string_buffer(3)
                offset = 0
                struct.pack_into('<HB', prop, offset, self.PROP_NOTIFY_RSP, 0x0)
                offset += 3
                self.serial_send(self.craete_frame(self.creat_resp(self.PROP_MSGID_RSP, reqseq, 0x0, prop)))
                ret = "success"

        return ret, netlink, rssi


    def onoff_status(self, onoff, timeout):
        action = ctypes.create_string_buffer(3)
        offset = 0

        struct.pack_into('<H', action, offset, self.ACTION_ONOFF_REQ)
        offset += 2

        struct.pack_into('<B', action, offset, onoff)
        offset += 1

        self.seqnum += 1
        self.serial_send(self.craete_frame(self.create_req(self.ACTION_MSGID_REQ, self.seqnum, action)))
        rspseq, payload = self.should_recv(self.ACTION_MSGID_RSP, self.seqnum, timeout)
        if payload == '':
            return '%s timeout in %dms' % (sys._getframe().f_code.co_name, timeout)
        else:
            print 'recv:' + binascii.b2a_hex(payload)
            actmsgid, opcode = struct.unpack_from('<HB', payload, 0x0)
            if actmsgid != self.ACTION_ONOFF_RSP or opcode != 0x00:
                return "act msg id 0x%x opcode 0x%x err" % (actmsgid, opcode)
            else:
                return "success"


    def reboot(self, timeout):
        action = ctypes.create_string_buffer(2)
        offset = 0

        struct.pack_into('<H', action, offset, self.ACTION_REBOOT_REQ)
        offset += 2

        self.seqnum += 1
        self.serial_send(self.craete_frame(self.create_req(self.ACTION_MSGID_REQ, self.seqnum, action)))
        rspseq, payload = self.should_recv(self.ACTION_MSGID_RSP, self.seqnum, timeout)
        if payload == '':
            return '%s timeout in %dms' % (sys._getframe().f_code.co_name, timeout)
        else:
            print 'recv:' + binascii.b2a_hex(payload)
            actmsgid, = struct.unpack_from('<H', payload, 0x0)
            if actmsgid != self.ACTION_REBOOT_RSP:
                return "act msg id err 0x%x" % actmsgid
            else:
                return "success"


    def port_testing(self, timeout):
        action = ctypes.create_string_buffer(102)
        offset = 0

        struct.pack_into('<H', action, offset, self.ACTION_TEST_REQ)
        offset += 2

        for val in range(0, 100):
            struct.pack_into('B', action, offset, 0x77)
            offset += 1

        self.seqnum += 1
        self.serial_send(self.craete_frame(self.create_req(self.ACTION_MSGID_REQ, self.seqnum, action)))
        rspseq, payload = self.should_recv(self.ACTION_MSGID_RSP, self.seqnum, timeout)
        if payload == '':
            return '%s timeout in %dms' % (sys._getframe().f_code.co_name, timeout)
        else:
            print 'recv:' + binascii.b2a_hex(payload)
            actmsgid, = struct.unpack_from('<H', payload, 0x0)
            if actmsgid != self.ACTION_TEST_RSP:
                return "act msg id err 0x%x" % actmsgid
            else:
                return "success"

    def get_uptime_mac(self, timeout):
        uptime = 0
        mac = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]

        prop = ctypes.create_string_buffer(5)
        offset = 0
        struct.pack_into('<HBBB', prop, offset, self.PROP_GET_REQ, 0x02, 0x74, 0x75)
        offset += 5

        self.seqnum += 1
        self.serial_send(self.craete_frame(self.create_req(self.PROP_MSGID_REQ, self.seqnum, prop)))
        rspseq, payload = self.should_recv(self.PROP_MSGID_RSP, self.seqnum, timeout)
        if payload == '':
            ret = '%s timeout in %dms' % (sys._getframe().f_code.co_name, timeout)
        else:
            print 'recv:' + binascii.b2a_hex(payload)
            getmsgid, opcode, infonum = struct.unpack_from('<HBB', payload, 0x0)
            if getmsgid != self.PROP_GET_RSP:
                ret = "prop msg id 0x%x" % (getmsgid)
            else:
                payload = payload[4:]
                ret = "success"

                while infonum:
                    print 'recv:' + binascii.b2a_hex(payload)
                    infotype, = struct.unpack_from('<B', payload, 0x0)
                    if infotype == 0x74:
                        uptime, = struct.unpack_from('<I', payload, 0x02)
                        payload = payload[6:]
                    elif infotype == 0x75:
                        mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] = struct.unpack_from('<BBBBBB', payload, 0x2)
                        payload = payload[8:]
                    else:
                        ret = "unknow infotype 0x%x" % infotype
                        break

                    infonum -= 1

        return ret, uptime, mac


    def wait_onoff_req(self, timeout):
        onoff = -1

        reqseq, payload = self.should_recv(self.ACTION_MSGID_REQ, 0x0000, timeout)
        if payload == '':
            ret = '%s timeout in %dms' % (sys._getframe().f_code.co_name, timeout)
        else:
            print 'recv:' + binascii.b2a_hex(payload)
            actmsgid, onoff = struct.unpack_from('<HB', payload, 0x0)
            if actmsgid != self.ACTION_ONOFF_REQ:
                ret = "act msg id 0x%x err " % (actmsgid)
            else:
                # response
                prop = ctypes.create_string_buffer(3)
                offset = 0
                struct.pack_into('<HB', prop, offset, self.ACTION_ONOFF_RSP, 0x0)
                offset += 3
                self.serial_send(self.craete_frame(self.creat_resp(self.ACTION_MSGID_RSP, reqseq, 0x0, prop)))
                ret = "success"

        return ret, onoff


if __name__ == '__main__':
    msg = OppleSerialMsg(com='COM8', baudrate=9600)
    msg.onoff_status(1, 1)

